package com.shelpe.services.simbasync.controller;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.shelpe.services.simbasync.dto.RptCustomerDto;
import com.shelpe.services.simbasync.dto.UserDto;
import com.shelpe.services.simbasync.exception.ApiException;
import com.shelpe.services.simbasync.exception.RequestParamException;
import com.shelpe.services.simbasync.model.RptCustomer;
import com.shelpe.services.simbasync.model.RptCustomerRealtime;
import com.shelpe.services.simbasync.model.SubwayTokens;
import com.shelpe.services.simbasync.model.Users;
import com.shelpe.services.simbasync.model.UsersWithBLOBs;
import com.shelpe.services.simbasync.service.AuthService;
import com.shelpe.services.simbasync.service.SyncRecordService;
import com.shelpe.services.simbasync.service.UserService;
import com.shelpe.services.simbasync.service.simbaproxy.AuthorizeProxy;
import com.shelpe.services.simbasync.service.simbaproxy.AuthorizeProxy.AuthorizeResponse;
import com.shelpe.services.simbasync.service.simbaproxy.UserProxy;
import com.shelpe.services.simbasync.service.simbaproxy.UserProxy.RptCustomerRealtimeResponse;
import com.shelpe.services.simbasync.service.simbaproxy.UserProxy.RptCustomerRealTimeDto;
import com.shelpe.services.simbasync.util.Util;

@RestController
@RequestMapping(value="/simbasync")
public class UserController {
	
	private static final Logger logger = LoggerFactory.getLogger(UserController.class);
	
	private static final Map<String, Date> requestPool = new HashMap<String, Date>();
	
	@Autowired
	private UserService service;
	
	@Autowired
	private UserProxy proxy;
	
	@Autowired
	private AuthService auth;
	
	@Autowired
	private AuthorizeProxy authProxy;
	
	@Autowired
	private SyncRecordService syncService;
	
	private final ExecutorService executor = Executors.newFixedThreadPool(4);
	
	@RequestMapping("/tenants/{user}/@baseuser")
	@ResponseBody
	public String syncUserInfo(@PathVariable("user") Long userId){
		
		String fields = "user_id,nick,seller_credit,type,is_golden_seller";
		
		return proxy.getSellerInfo(userId, fields);
	}
	
	@RequestMapping("/tenants/{user}/@authorize")
	@ResponseBody
	public RptCustomerDto syncAuthorizeByUser(@PathVariable("user") String user){//可能会添加代理用户
		
		boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		
		return isNumber ? syncAuthorize(null, Long.valueOf(user)) : syncAuthorize(user, null);
	}
	
	@RequestMapping("/tenants/{user_id}/@purchaseuser")//添加订购用户
	@ResponseBody
	public UserDto createAndAddPurchaseUser(@PathVariable("user_id") Long userId, @RequestParam("nick") String nick){
		
		UserDto dto = new UserDto();
		if(userId == null || nick == null){
			dto.setMessage("error");
			return dto;
		}
		
		UsersWithBLOBs user = new UsersWithBLOBs();
		user.setAuthorizedTo(0L);
		user.setUserId(userId);
		user.setNick(nick);
		
		dto.setAddNum(service.createAndAddPurchaseUser(user));//增加的数目
		
		dto.setUserId(userId);
		dto.setNick(nick);
		dto.setUserType(1);//订购用户
		Date deadLine = user.getPurchaseExpires();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		dto.setDeadLine(sdf.format(deadLine));
		
		return dto;
	}
	
	@RequestMapping("/tenants/{user}/@subwaytoken")
	@ResponseBody
	public RptCustomerDto syncSubwayTokenByUser(@PathVariable("user") String user){
		
		boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		
		return isNumber ? syncSubwayToken(null, Long.valueOf(user)) : syncSubwayToken(user, null);
	}
	
	@RequestMapping("/tenants/{user}/@customer")
	@ResponseBody
	public RptCustomerDto syncUserBalance(@PathVariable("user") String user){
		
		boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		
		return isNumber ? syncUserBalance(null, Long.valueOf(user)) : syncUserBalance(user, null);
	}
	
	@RequestMapping("/tenants")
	@ResponseBody
	public RptCustomerDto syncUserBalance(){
		
		List<Users> list = service.getAllUsers();
		RptCustomerDto dto = new RptCustomerDto();
		Date beginTime = new Date();
		int  updateNum = 0;
		for(Users user : list){
			dto = syncUserBalance(null, user.getUserId());
			updateNum += dto.getUpdateNum();
		}
		
		Date endTime = new Date();
		dto.setUpdateNum(updateNum);
		dto.setDurTime(endTime.getTime() - beginTime.getTime());
		
		return dto;
	}
	
	@RequestMapping("/tenants/{user}/@rptcustomer")
	@ResponseBody
	public RptCustomerDto syncRptCustomerByUser(@PathVariable("user")  String user, 
			@RequestParam(value = "start_date", required = false)  String startDate, @RequestParam(value = "end_date", required = false)  String endDate,
			@RequestParam(value = "record", required = false) String record){
		
		boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		
		return isNumber ? syncRptCustomer(null, Long.valueOf(user), startDate, endDate, record) : syncRptCustomer(user, null, startDate, endDate, record);
	}
	
	@RequestMapping("/tenants/{user}/@rptcustomerasyn")
	@ResponseBody
	public DeferredResult<RptCustomerDto> syncRptCustomerByUserAsyn(@PathVariable("user") final String user, 
			@RequestParam(value = "start_date", required = false) final String startDate, @RequestParam(value = "end_date", required = false) final String endDate,
			@RequestParam(value = "record", required = false) final String record){
		
		final DeferredResult<RptCustomerDto> result = new DeferredResult<RptCustomerDto>();
		final boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		CompletionService<RptCustomerDto> comletionService = new ExecutorCompletionService<RptCustomerDto>(executor); 
		
		comletionService.submit(new Callable<RptCustomerDto>(){
                                                                                                                                                                                                    
			@Override
			public RptCustomerDto call() throws Exception {
				// TODO Auto-generated method stub
				RptCustomerDto dto = null;
				if(isNumber){
					dto = syncRptCustomer(null, Long.valueOf(user), startDate, endDate, record);
				}else{
					dto = syncRptCustomer(user, null, startDate, endDate, record);
				}
				
				result.setResult(dto);
				
				return dto;
			}});
		
		return result;
	}
	
	
	@RequestMapping("/tenants/{user}/@rptcustomerrealtime")
	@ResponseBody
	public RptCustomerDto syncRptCustomerRealTimeByUser(@PathVariable("user") String user){
		
		boolean isNumber = user.matches("-?[0-9]+.*[0-9]*");
		
		return isNumber ? syncRptCustomerRealTime(null, Long.valueOf(user)) : syncRptCustomerRealTime(user, null);
		
	}

	private RptCustomerDto syncAuthorize(String nick, Long userId){
		
		if(userId == null){
			userId = service.getUserIdByNick(nick);
		}
		
		if(nick == null){
			nick = service.getNickByUserId(userId);
		}
		
		if(nick == null || userId == null){
			logger.warn("nick or userId is null, do nothing");
			throw new RequestParamException();
		}
		
		logger.info(">>>syncAuthorize nick {}, userId {}", nick, userId);
		
		Date beginProcessTime = new Date();
		RptCustomerDto dto = new RptCustomerDto();
		dto.setNick(nick);
		dto.setUserId(userId);
		dto.setProcess("syncAuthorize");
		
		AuthorizeResponse res = authProxy.getAuthorizeUserList(userId);
		List<Long> updateUserIds = new ArrayList<Long>();
		List<Long> newUserIds = new ArrayList<Long>();
		
		if(res != null && res.getRes() != null && res.getRes().getNicks() != null){
			List<String> nicks = res.getRes().getNicks().getNick();
			int updateNum = 0;
			int addNum = 0;
			if(nicks != null && nicks.size() > 0){
			
				for(String nickSpec : nicks){
					List<Users> users = service.getUserByNick(nickSpec);
					
					if(users != null && users.size() > 0){
						for(Users u : users){
							if(!u.getAuthorizedTo().equals(userId)){
								u.setAuthorizedTo(userId);
								service.updateUserSelective(u);
								updateUserIds.add(u.getUserId());
								updateNum++;
							}
						}
					}else{
						//new User
						UsersWithBLOBs u = new UsersWithBLOBs();
						u.setNick(nickSpec);
						u.setAuthorizedTo(userId);
						Long newUserId = service.createAndAddProxyUser(u);
						if(newUserId != null){
							newUserIds.add(newUserId);
							addNum++;
						}
					}
				}
			}
			dto.setAddNum(addNum);
			dto.setUpdateNum(updateNum);
		}else{
			if(res != null){
				logger.warn("syncAuthorize something is wrong, error: {}", res.getError());
			}
		}
		
		dto.setUpdateUserIds(updateUserIds);
		dto.setNewUserIds(newUserIds);
		Date endProcessTime = new Date();
		long durTime = endProcessTime.getTime() - beginProcessTime.getTime();
		dto.setDurTime(durTime);
		
		return dto;
	}	
	
	
	private RptCustomerDto syncSubwayToken(String nick, Long userId){
		
		if(userId == null){
			userId = service.getUserIdByNick(nick);
		}
		
		if(nick == null){
			nick = service.getNickByUserId(userId);
		}
		
		if(nick == null || userId == null){
			logger.warn("nick or userId is null, do nothing");
			throw new RequestParamException();
		}
		Date beginProcessTime = new Date();
		RptCustomerDto dto = new RptCustomerDto();
		dto.setNick(nick);
		dto.setUserId(userId);
		dto.setProcess("syncSubwayToken");
		
		String response = null;
		
		try{
			response = proxy.getSubwayTokenByNick(nick);
		}catch(Exception e){
			logger.warn("Maybe something is wrong or can not find token, userId {}", userId);
		}
		
		if(response != null && response.contains("invalid-sessionkey")){
			dto.setMessage("invalid-sessionkey");
			logger.warn("can not get any info from proxy, maybe something is wrong, nick is {}, response {}", nick, response);
			return dto;
		}
		
		SubwayTokens token = service.parseJsonToSubwayTokens(response);
		token.setUserId(userId);
		SubwayTokens tokenOld = service.getSubwayTokenByUserId(userId);
		if(tokenOld != null){
			dto.setUpdateNum(service.updateSubwayTokens(token));
		}else{
			dto.setAddNum(service.addSubwayTokens(token));
		}
		
		Date endProcessTime = new Date();
		long durTime = endProcessTime.getTime() - beginProcessTime.getTime();
		dto.setDurTime(durTime);
		
		return dto;
	}	
	
	private RptCustomerDto syncUserBalance(String nick, Long userId){
		
		Users user = null;
	
		if(userId != null){
			user = service.getUserByUserId(userId);
		}
	
		if(nick != null){
			List<Users> list = service.getUserByNick(nick);
			if(list != null && list.size() > 0){
				user = list.iterator().next();
			}
		}
	       
		if(user == null ){
			logger.warn("can not find user , do nothing");
			throw new RequestParamException();
		}
		
		logger.info(">>>syncUserBalance nick {}, userId {}", nick, userId);
		
		RptCustomerDto dto = new RptCustomerDto();
		dto.setNick(user.getNick());
		dto.setUserId(userId);
		dto.setProcess("syncUserBalance");
      
		Date beginProcessTime = new Date();
		
		String response = null;
		try{
			response = proxy.getAccountBalance(user.getNick());
		}catch(Exception e){
			logger.warn("Maybe something is wrong or can not find token, userId {}", userId);
		}
		
		if(response != null && response.contains("simba_account_balance_get_response")){
			JSONObject object = JSON.parseObject(response);
			JSONObject balanceObject = object.getJSONObject("simba_account_balance_get_response");
			if(balanceObject.containsKey("balance")){
				String balance = balanceObject.getString("balance");
				user.setBalance(BigDecimal.valueOf(NumberUtils.toDouble(balance, 0.00d)));
				dto.setUpdateNum(service.updateUserSelective(user));
			}
		}else{
			logger.warn("can not get any info from proxy, maybe something is wrong, nick is {}， response {}", user.getNick(), response);
			if(response != null && response.contains("isv.invalid-permission")){//没有权限
				dto.setMessage("invalid-permission");
				return dto;
			}else if(response != null && response.contains("invalid-sessionkey")){
				dto.setMessage("invalid-sessionkey");
				logger.warn("can not get any info from proxy, maybe something is wrong, nick is {}, response {}", nick, response);
				return dto;
			}else if(response != null && response.contains("无法根据nick获取直通车帐号信息")){
				dto.setMessage("无法根据nick获取直通车帐号信息");
				logger.warn("can not get any info from proxy, maybe something is wrong, nick is {}, response {}", nick, response);
				return dto;
			}else if(response == null){
				dto.setMessage("get no information");
				return dto;
			}else{
				throw new ApiException();
			}
		}
		
		Date endProcessTime = new Date();
		long durTime = endProcessTime.getTime() - beginProcessTime.getTime();
		dto.setDurTime(durTime);
		
		return dto;
	}
	
	private RptCustomerDto syncRptCustomer(String nick, Long userId, String startDate, String endDate, String record){//整体状况
	
		if(userId == null){
			userId = service.getUserIdByNick(nick);
		}
		
		if(nick == null){
			nick = service.getNickByUserId(userId);
		}
		
		if(nick == null || userId == null){
			logger.warn("nick or userId is null, do nothing");
			throw new RequestParamException();
		}
		logger.info(">>>syncRptCustomer nick {}, userId {}", nick, userId);
		RptCustomerDto dto = new RptCustomerDto();
		dto.setNick(nick);
		dto.setUserId(userId);
		dto.setProcess("syncRptCustomer");
		Date beginProcessTime = new Date();
		
		String requestParam = Util.createKey("rpt", userId.toString(), null, null, endDate);
		
		if(requestPool.containsKey(requestParam)){
		
			Date lastProcessDate = requestPool.get(requestParam);
			if(beginProcessTime.getTime() - lastProcessDate.getTime() > Util.MilliSecondsOfMin){
				requestPool.remove(requestParam);
				logger.info(">>>syncRptCustomer nick {}, userId {} process out time, restart new sync task", nick, userId);
			}else{
				dto.setMessage("On processing");
				logger.info(">>>syncRptCustomer nick {}, userId {} request too freq", nick, userId);
				return dto;
			}
		}

		requestPool.put(requestParam, beginProcessTime);
		
		Map<String, List<Long>> dateRange = new HashMap<String, List<Long>>();
		Map<String, String> map = parseDateRange(userId, startDate, endDate, dateRange);
		
		logger.info(">>> startTime {}, endTime {}, pageSize {}", map.get("startTime"), map.get("endTime"), map.get("pageSize"));
		if(map.get("startTime").compareTo(map.get("endTime")) < 1){//需要同步
					
			String subwayToken = auth.getSubwayTokenByUserId(userId);
			
			if(subwayToken == null){
				logger.warn("can not find subwaytoken, nick is {}", nick);
				dto.setMessage("can not find subwaytoken");
				requestPool.remove(requestParam);
				return dto;
			}
			
			String source = "1,2,4,5";
			int pageSize = 5000;
			pageSize = NumberUtils.toInt(map.get("pageSize"), 5000);
			
			String response = null;
				
			int pageNo = 1;
			List<RptCustomer> listTotal = new ArrayList<RptCustomer>();
			List<RptCustomer> subList = null;
			
			try{
				
				do{
					response = proxy.getRptCustomerBase(subwayToken, nick, source, map.get("startTime"), map.get("endTime"), pageSize, pageNo);
					
					if(response != null && response.contains("isv.invalid-permission")){//没有权限
						dto.setMessage("invalid-permission");
						requestPool.remove(requestParam);
						return dto;
					}else if(response != null && response.contains("invalid-sessionkey")){
						dto.setMessage("invalid-sessionkey");
						logger.warn("can not get any info from proxy, maybe something is wrong, nick is {}, response {}", nick, response);
						requestPool.remove(requestParam);
						return dto;
					}else if(response == null){
						dto.setMessage("get no information");
						requestPool.remove(requestParam);
						return dto;
					}
					
					subList = service.parseJsonToRptCustomerBase(response);
					if(subList != null && subList.size() > 0){
						listTotal.addAll(subList);
					}
					pageNo++;
				}while(subList != null && subList.size() == pageSize && pageSize > 0);
				
				int restSize = 0; 
				//int totalEffect = 0;
				pageNo = 1;
				if(listTotal != null && listTotal.size() > 0 && pageSize > 0){
					int lastDayRecordTotal = 0;
					int lastDayNoDataNum = 0;
					do{
						response = proxy.getRptCustomerEffect(subwayToken, nick, source, map.get("startTime"), map.get("endTime"), pageSize, pageNo);
						Map<String, Integer> lastDayRecord = new HashMap<String, Integer>();
						restSize = service.combineRptCustomerEffect(response, lastDayRecord, listTotal);
						lastDayRecordTotal += lastDayRecord.get("total");
						lastDayNoDataNum += lastDayRecord.get("lost");
						//totalEffect+=restSize;
						pageNo++;
					}while(restSize == pageSize);
					
					if(record != null && record.equalsIgnoreCase("yes") 
							&& lastDayRecordTotal > 0 && lastDayNoDataNum == lastDayRecordTotal){//有基础数据，没有效果数据
						
						logger.warn("syncRptCustomer, base data num {}, effect data num is zero, userId is {}", listTotal.size(), userId);
						syncService.updateSyncRecordByProcess("syncRptCustomer", 1);
						dto.setMessage("last day has no data yet");
						requestPool.remove(requestParam);
						
						return dto;//整个事务舍弃
					}
					
					service.UpdateRptCustomerData(userId, listTotal);
				}
	
			}catch(Exception e){
				requestPool.remove(requestParam);
				throw e;
			}
				
			deleteRptDataByDateRange(dateRange);
			dto.setAddNum(service.insertRptCustomerBatch(listTotal));
		}
		
		Date endProcessTime = new Date();
		long durTime = endProcessTime.getTime() - beginProcessTime.getTime();
		dto.setDurTime(durTime);
		
		requestPool.remove(requestParam);
		
		return dto;
	}
	
	
	private RptCustomerDto syncRptCustomerRealTime(String nick, Long userId){
		
		if(nick == null){
			nick = service.getNickByUserId(userId);
		}
		
		if(nick == null){
			logger.warn("nick is null, do nothing");
			throw new RequestParamException();
		}
		
		logger.info(">>>syncRptCustomerRealTime nick {}, userId {}", nick, userId);
		RptCustomerDto dto = new RptCustomerDto();
		dto.setNick(nick);
		dto.setUserId(userId);
		dto.setProcess("syncRptCustomerRealTime");
		Date beginProcessTime = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		String theDate = sdf.format(beginProcessTime);
		
		RptCustomerRealtimeResponse res = proxy.getRptCustomerRealtime(nick, theDate);
					
		if(res != null && res.getResponse() != null && res.getResponse().getResults() != null){
			
			List<RptCustomerRealTimeDto> realDto = res.getResponse()
													.getResults()
													.getDto();	
			List<RptCustomerRealtime> rptRealTimeList = new ArrayList<RptCustomerRealtime>();
			if(realDto != null && realDto.size() > 0){
				service.convert(userId, realDto, rptRealTimeList);
				dto.setAddNum(service.insertRptCustomerRealtimeBatch(rptRealTimeList));
			}
		}else{
			
			dto.setMessage("get no information");
		}
		
		Date endProcessTime = new Date();
		long durTime = endProcessTime.getTime() - beginProcessTime.getTime();
		dto.setDurTime(durTime);
		
		return dto;		
	}
	
	
/************辅助函数******************/
	
	private Map<String, String> parseDateRange(Long userId, String startDate, String endDate, Map<String, List<Long>> dateRange){
		
		Integer pageSize = 5000;
	
		Map<String, String> map = null;
		if(startDate == null || endDate == null){
			Date dateOld = service.getLastTimeFromRpt(userId); 
			map = Util.getDateRange(dateOld);
			Util.adjustTaskRange(map);//调整时间段，保证每次更新至少15天的数据，解决数据不准确的问题
			startDate = map.get("startTime");
			endDate = map.get("endTime");
			pageSize = NumberUtils.toInt(map.get("pageSize"), 5000);
		}
	
		if(startDate.compareTo(endDate) < 1){
			StringBuilder sb = new StringBuilder();
			sb.append(startDate);
			sb.append(",");
			sb.append(endDate);
			String range = sb.toString();
			if(dateRange.containsKey(range)){
				dateRange.get(range).add(userId);
			}else{
				List<Long> idsList = new ArrayList<Long>();
				idsList.add(userId);
				dateRange.put(range, idsList);
			}
		}
		
		if(map == null){
			map = new HashMap<String, String>();
			map.put("startTime", startDate);
			map.put("endTime", endDate);
			map.put("pageSize", pageSize.toString());
		}
		
		return map;
	}
	
	private void deleteRptDataByDateRange(Map<String, List<Long>> dateRange){
		
		for (Map.Entry<String, List<Long>> entry : dateRange.entrySet()){
			
			String key = entry.getKey();
			String[] range = key.split(",");
			Date startDate = new Date();
			Date endDate = startDate;
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
			try {
				startDate = sdf.parse(range[0]);
				endDate = sdf.parse(range[1]);
			} catch (ParseException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		
			List<Long> userIds = entry.getValue();
			service.deleteRptCustomerByDateRange(userIds, startDate, endDate);
		}
	}

}